﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Dns.cs" company="Rolosoft Ltd">
//   © Rolosoft Ltd
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

#region License

// Copyright 2013 Rolosoft Ltd
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace Rsft.Net.Dns
{
    #region Usings

    using System;
    using System.Diagnostics.Contracts;
    using System.Globalization;
    using System.Net;
    using System.Security.Permissions;
    using System.Threading;
    using System.Threading.Tasks;

    using Rsft.Net.Dns.Diagnostics;
    using Rsft.Net.Dns.Entities;
    using Rsft.Net.Dns.Extensions;
    using Rsft.Net.Dns.Interfaces;

    #endregion
    
    /// <summary>
    ///     Provides advanced domain name resolution functionality.
    /// </summary>
    /// <remarks>
    ///     Direct replacement for <see cref="System.Net.Dns" />. Supplements existing methods with advanced DNS resolutions for DNS records per RFC1034, RFC1035, RFC1886, RFC2915.
    /// </remarks>
    public static class Dns
    {
        #region Static Fields

        /// <summary>
        /// The DNS permission.
        /// </summary>
        private static readonly DnsPermission DnsPermission = new DnsPermission(PermissionState.Unrestricted);

        /// <summary>
        ///     The configuration factory
        /// </summary>
        private static readonly IConfigurationFactory ConfigurationFactory = new ConfigurationFactoryDefault();

        /// <summary>
        ///     The reader writer lock slim configuration
        /// </summary>
        private static readonly ReaderWriterLockSlim ReaderWriterLockSlimConfiguration =
            new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        
        /// <summary>
        ///     The configuration
        /// </summary>
        private static IConfiguration configuration = ConfigurationFactory.Create();

        /// <summary>
        ///     The resolver factory
        /// </summary>
        private static readonly ResolverDefault Resolver = ResolverFactoryDefault.CreateResolver(configuration);
        
        /// <summary>
        /// The logger.
        /// </summary>
        private static readonly ILogger Logger = LoggerFactoryDefault.Create(null);
        
        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes static members of the <see cref="Dns"/> class.
        /// </summary>
        static Dns()
        {
            Resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);
        }

        #endregion

        #region Public Events

        /// <summary>
        ///     Occurs when [verbose event].
        /// </summary>
        public static event EventHandler<VerboseEventArgs> VerboseEvent;

        #endregion

        #region Public Properties

        /// <summary>
        ///     Gets or sets the DNS configuration.
        /// </summary>
        /// <value>
        ///     The configuration.
        /// </value>
        public static IConfiguration DnsConfiguration
        {
            get
            {
                ReaderWriterLockSlimConfiguration.EnterReadLock();
                var rtn = configuration;
                ReaderWriterLockSlimConfiguration.ExitReadLock();
                return rtn;
            }

            set
            {
                ReaderWriterLockSlimConfiguration.EnterWriteLock();
                configuration = value;
                ReaderWriterLockSlimConfiguration.ExitWriteLock();
            }
        }

        #endregion
        
        #region Public Methods and Operators

        /// <summary>
        /// Asynchronously returns the Internet Protocol (IP) addresses for the specified host.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <param name="hostNameOrAddress">
        /// The host name or IP address to resolve.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="state">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostNameOrAddress"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostNameOrAddress"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostNameOrAddress"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="hostNameOrAddress"/> is an invalid IP address.
        /// </exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginGetHostAddresses(
            string hostNameOrAddress, AsyncCallback requestCallback, object state)
        {
            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            if (hostNameOrAddress.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostNameOrAddress", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostByAddress : {0}", hostNameOrAddress));

            return Resolver.GetHostAddressesAsync(hostNameOrAddress, CancellationToken.None)
                           .AsApm(requestCallback, state);
        }

        /// <summary>
        /// Begins an asynchronous request for <see cref="T:System.Net.IPHostEntry"/> information about the specified DNS host name.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <param name="hostName">
        /// The DNS name of the host.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="stateObject">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostName"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error was encountered executing the DNS query.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("BeginGetHostByName is obsoleted for this type, please use BeginGetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginGetHostByName(
            string hostName, AsyncCallback requestCallback, object stateObject)
        {
            if (hostName == null)
            {
                throw new ArgumentNullException("hostName");
            }

            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostByName : {0}", hostName));

            return Resolver.GetHostByNameAsync(hostName, CancellationToken.None).AsApm(requestCallback, stateObject);
        }

        /// <summary>
        /// Asynchronously resolves a host name or IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// The host name or IP address to resolve.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="stateObject">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// requestCallback
        ///     or
        ///     hostNameOrAddress
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// hostNameOrAddress;The length of parameter is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostNameOrAddress"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostNameOrAddress"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostNameOrAddress"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="hostNameOrAddress"/> is an invalid IP address.
        /// </exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginGetHostEntry(
            string hostNameOrAddress, AsyncCallback requestCallback, object stateObject)
        {
            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            if (hostNameOrAddress.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostNameOrAddress", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostEntry : {0}", hostNameOrAddress));
            
            return Resolver.GetHostEntryAsync(hostNameOrAddress, CancellationToken.None)
                           .AsApm(requestCallback, stateObject);
        }

        /// <summary>
        /// Asynchronously resolves an IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <param name="address">
        /// The IP address to resolve.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="stateObject">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="address"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="address"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="address"/> is an invalid IP address.
        /// </exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginGetHostEntry(
            IPAddress address, AsyncCallback requestCallback, object stateObject)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();
            Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "BeginGetHostEntry : {0}", address));

            return Resolver.GetHostEntryAsync(address, CancellationToken.None).AsApm(requestCallback, stateObject);
        }

        /// <summary>
        /// Begins the query.
        /// </summary>
        /// <param name="name">
        /// The name.
        /// </param>
        /// <param name="queryType">
        /// Type of the query.
        /// </param>
        /// <param name="queryClassType">
        /// Type of the query class.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="state">
        /// The state.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// name
        ///     or
        ///     asyncCallback is null
        /// </exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginQuery(
            string name, QType queryType, QClassType queryClassType, AsyncCallback asyncCallback, object state)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            if (asyncCallback == null)
            {
                throw new ArgumentNullException("asyncCallback");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "BeginQuery : name={0}, queryType={1}, queryClassType={2}", name, queryType, queryClassType));

            return Resolver.QueryAsync(name, queryType, CancellationToken.None, queryClassType)
                           .AsApm(asyncCallback, state);
        }

        /// <summary>
        /// Begins an asynchronous request to resolve a DNS host name or IP address to an <see cref="T:System.Net.IPAddress"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <param name="hostName">
        /// The DNS name of the host.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="stateObject">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostName"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// The caller does not have permission to access DNS information.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("BeginResolve is obsoleted for this type, please use BeginGetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginResolve(string hostName, AsyncCallback requestCallback, object stateObject)
        {
            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            if (string.IsNullOrEmpty(hostName))
            {
                throw new ArgumentNullException("hostName");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            Logger.Verbose(
                (int)EventId.CommandReceived,
                string.Format(CultureInfo.InvariantCulture, "BeginResolve : {0}", hostName));

            return Resolver.ResolveAsync(hostName, CancellationToken.None).AsApm(requestCallback, stateObject);
        }

        /// <summary>
        /// Ends an asynchronous request for DNS information.
        /// </summary>
        /// <returns>
        /// An array of type <see cref="T:System.Net.IPAddress"/> that holds the IP addresses for the host specified by the
        ///     <paramref name="hostNameOrAddress"/>
        ///     parameter of
        ///     <see cref="M:Rsft.Net.Dns.BeginGetHostAddresses(System.String,System.AsyncCallback,System.Object)"/>
        ///     .
        /// </returns>
        /// <param name="asyncResult">
        /// An <see cref="T:System.IAsyncResult"/> instance returned by a call to the
        ///     <see cref="M:Rsft.Net.Dns.BeginGetHostAddresses(System.String,System.AsyncCallback,System.Object)"/>
        ///     method.
        /// </param>
        public static IPAddress[] EndGetHostAddresses(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();

            var task = (Task<IPAddress[]>)asyncResult;

            IPAddress[] addresses;

            try
            {
                addresses = task.Result;
            }
            catch (AggregateException aggregateException)
            {
                var exception = aggregateException.Flatten().InnerException;

                throw exception;
            }

            Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "EndGetHostAddresses : Count# {0}", addresses.Length));

            task.Dispose();

            return addresses;
        }

        /// <summary>
        /// Ends an asynchronous request for DNS information.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> object that contains DNS information about a host.
        /// </returns>
        /// <param name="asyncResult">An <see cref="T:System.IAsyncResult"/> instance that is returned by a call to the <see cref="M:System.Net.Dns.BeginGetHostByName(System.String,System.AsyncCallback,System.Object)"/> method.</param><exception cref="T:System.ArgumentNullException"><paramref name="asyncResult"/> is null. </exception><PermissionSet><IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/><IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/><IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/></PermissionSet>
        [Obsolete("EndGetHostByName is obsoleted for this type, please use EndGetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry EndGetHostByName(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();

            var task = (Task<IPHostEntry>)asyncResult;

            IPHostEntry hostEntry;

            try
            {
                hostEntry = task.Result;
            }
            catch (AggregateException aggregateException)
            {
                var exception = aggregateException.Flatten().InnerException;

                throw exception;
            }

            Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "EndGetHostByName : {0}", hostEntry.HostName));

            task.Dispose();

            return hostEntry;
        }

        /// <summary>
        /// Ends an asynchronous request for DNS information.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance that contains address information about the host.
        /// </returns>
        /// <param name="asyncResult">
        /// An <see cref="T:System.IAsyncResult"/> instance returned by a call to an
        ///     <see cref="Overload:Rsft.Net.Dns.BeginGetHostEntry"/>
        ///     method.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="asyncResult"/> is null.
        /// </exception>
        public static IPHostEntry EndGetHostEntry(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();

            var task = (Task<IPHostEntry>)asyncResult;

            IPHostEntry hostEntry;

            try
            {
                hostEntry = task.Result;
            }
            catch (AggregateException aggregateException)
            {
                var exception = aggregateException.Flatten().InnerException;

                throw exception;
            }

            Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "EndGetHostEntry : {0}", hostEntry.HostName));

            task.Dispose();

            return hostEntry;
        }

        /// <summary>
        /// Ends the query.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="Response"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// asyncResult is null
        /// </exception>
        public static Response EndQuery(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();

            var task = (Task<Response>)asyncResult;

            Response response;

            try
            {
                response = task.Result;
            }
            catch (AggregateException aggregateException)
            {
                var exception = aggregateException.Flatten().InnerException;

                throw exception;
            }

            Logger.Verbose((int)EventId.CommandProcessed, string.Format(CultureInfo.InvariantCulture, "EndQuery"));

            task.Dispose();

            return response;
        }

        /// <summary>
        /// Ends an asynchronous request for DNS information.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> object that contains DNS information about a host.
        /// </returns>
        /// <param name="asyncResult">
        /// An <see cref="T:System.IAsyncResult"/> instance that is returned by a call to the
        ///     <see cref="M:System.Net.Dns.BeginResolve(System.String,System.AsyncCallback,System.Object)"/>
        ///     method.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="asyncResult"/> is null.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        [Obsolete("EndResolve is obsoleted for this type, please use EndGetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry EndResolve(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();

            var task = (Task<IPHostEntry>)asyncResult;

            IPHostEntry hostEntry;

            try
            {
                hostEntry = task.Result;
            }
            catch (AggregateException aggregateException)
            {
                var exception = aggregateException.Flatten().InnerException;

                throw exception;
            }

            Logger.Verbose(
                (int)EventId.CommandProcessed,
                string.Format(CultureInfo.InvariantCulture, "EndResolve : {0}", hostEntry.HostName));

            task.Dispose();

            return hostEntry;
        }

        /// <summary>
        /// Returns the Internet Protocol (IP) addresses for the specified host.
        /// </summary>
        /// <returns>
        /// An array of type <see cref="T:System.Net.IPAddress"/> that holds the IP addresses for the host that is specified by the
        ///     <paramref name="hostNameOrAddress"/>
        ///     parameter.
        /// </returns>
        /// <param name="hostNameOrAddress">
        /// The host name or IP address to resolve.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostNameOrAddress"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostNameOrAddress"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostNameOrAddress"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="hostNameOrAddress"/> is an invalid IP address.
        /// </exception>
        public static IPAddress[] GetHostAddresses(string hostNameOrAddress)
        {
            if (hostNameOrAddress == null)
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            if (hostNameOrAddress.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostNameOrAddress", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            return Resolver.GetHostAddresses(hostNameOrAddress);
        }

        /// <summary>
        /// Creates an <see cref="T:System.Net.IPHostEntry"/> instance from an IP address.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </returns>
        /// <param name="address">
        /// An IP address.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="address"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="address"/>.
        /// </exception>
        /// <exception cref="T:System.FormatException">
        /// <paramref name="address"/> is not a valid IP address.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("GetHostByAddress is obsoleted for this type, please use GetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry GetHostByAddress(string address)
        {
            if (string.IsNullOrEmpty(address))
            {
                throw new ArgumentNullException("address");
            }

            Contract.EndContractBlock();

            /*Throw expected FormatException if invalid format*/
            IPAddress.Parse(address);

            return Resolver.GetHostByAddress(address);
        }

        /// <summary>
        /// Creates an <see cref="T:System.Net.IPHostEntry"/> instance from the specified <see cref="T:System.Net.IPAddress"/>.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/>.
        /// </returns>
        /// <param name="address">
        /// An <see cref="T:System.Net.IPAddress"/>.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="address"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="address"/>.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("GetHostByAddress is obsoleted for this type, please use GetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry GetHostByAddress(IPAddress address)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            Contract.EndContractBlock();

            return Resolver.GetHostByAddress(address);
        }

        /// <summary>
        /// Gets the DNS information for the specified DNS host name.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> object that contains host information for the address specified in
        ///     <paramref name="hostName"/>
        ///     .
        /// </returns>
        /// <param name="hostName">
        /// The DNS name of the host.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostName"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostName"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostName"/>.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("GetHostByName is obsoleted for this type, please use GetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry GetHostByName(string hostName)
        {
            if (string.IsNullOrEmpty(hostName))
            {
                throw new ArgumentNullException("hostName");
            }

            if (hostName.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostName", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            return Resolver.GetHostByName(hostName);
        }

        /// <summary>
        /// Resolves a host name or IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance that contains address information about the host specified in
        ///     <paramref name="hostNameOrAddress"/>
        ///     .
        /// </returns>
        /// <param name="hostNameOrAddress">
        /// The host name or IP address to resolve.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// The <paramref name="hostNameOrAddress"/> parameter is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostNameOrAddress"/> parameter is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error was encountered when resolving the <paramref name="hostNameOrAddress"/> parameter.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// The <paramref name="hostNameOrAddress"/> parameter is an invalid IP address.
        /// </exception>
        public static IPHostEntry GetHostEntry(string hostNameOrAddress)
        {
            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            if (hostNameOrAddress.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostNameOrAddress", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            return Resolver.GetHostEntry(hostNameOrAddress);
        }

        /// <summary>
        /// Resolves an IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance that contains address information about the host specified in
        ///     <paramref name="address"/>
        ///     .
        /// </returns>
        /// <param name="address">
        /// An IP address.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="address"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="address"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="address"/> is an invalid IP address.
        /// </exception>
        public static IPHostEntry GetHostEntry(IPAddress address)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            Contract.EndContractBlock();

            return Resolver.GetHostEntry(address);
        }

        /// <summary>
        ///     Gets the host name of the local computer.
        /// </summary>
        /// <returns>
        ///     A string that contains the DNS host name of the local computer.
        /// </returns>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        ///     An error is encountered when resolving the local host name.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission
        ///         class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
        ///         version="1" Unrestricted="true" />
        /// </PermissionSet>
        public static string GetHostName()
        {
            return Resolver.GetHostName();
        }

        /// <summary>
        /// Queries the specified name.
        /// </summary>
        /// <param name="name">
        /// The name.
        /// </param>
        /// <param name="queryType">
        /// Type of the query.
        /// </param>
        /// <param name="queryClassType">
        /// Type of the query class.
        /// </param>
        /// <returns>
        /// The <see cref="Response"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// name is null.
        /// </exception>
        public static Response Query(string name, QType queryType, QClassType queryClassType = QClassType.IN)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            Contract.EndContractBlock();

            return Resolver.Query(name, queryType, queryClassType);
        }

        /// <summary>
        /// Resolves a DNS host name or IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance that contains address information about the host specified in
        ///     <paramref name="hostName"/>
        ///     .
        /// </returns>
        /// <param name="hostName">
        /// A DNS-style host name or IP address.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostName"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostName"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostName"/>.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("Resolve is obsoleted for this type, please use GetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry Resolve(string hostName)
        {
            if (string.IsNullOrEmpty(hostName))
            {
                throw new ArgumentNullException("hostName");
            }

            if (hostName.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostName", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            return Resolver.Resolve(hostName);
        }

        /// <summary>
        /// Gets the host addresses async.
        /// </summary>
        /// <param name="hostNameOrAddress">The host name or address.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">hostNameOrAddress is null.</exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<IPAddress[]> GetHostAddressesAsync(string hostNameOrAddress, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(hostNameOrAddress))
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            return await Resolver.GetHostAddressesAsync(hostNameOrAddress, cancellationToken);
        }

        /// <summary>
        /// Gets the host addresses async.
        /// </summary>
        /// <param name="hostNameOrAddress">The host name or address.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">hostNameOrAddress is null.</exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<IPAddress[]> GetHostAddressesAsync(string hostNameOrAddress)
        {
            DnsPermission.Demand();
            return await GetHostAddressesAsync(hostNameOrAddress, CancellationToken.None);
        }

        /// <summary>
        /// Gets the host entry async.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">address</exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<IPHostEntry> GetHostEntryAsync(IPAddress address, CancellationToken cancellationToken)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            return await Resolver.GetHostEntryAsync(address, cancellationToken);
        }

        /// <summary>
        /// Gets the host entry async.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">address</exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<IPHostEntry> GetHostEntryAsync(IPAddress address)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            return await GetHostEntryAsync(address, CancellationToken.None);
        }

        /// <summary>
        /// Gets the host entry async.
        /// </summary>
        /// <param name="hostNameOrAddress">The host name or address.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">hostNameOrAddress</exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(hostNameOrAddress))
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            return await Resolver.GetHostEntryAsync(hostNameOrAddress, cancellationToken);
        }

        /// <summary>
        /// Gets the host entry async.
        /// </summary>
        /// <param name="hostNameOrAddress">The host name or address.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">hostNameOrAddress</exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress)
        {
            if (string.IsNullOrWhiteSpace(hostNameOrAddress))
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            return await GetHostEntryAsync(hostNameOrAddress, CancellationToken.None);
        }

        /// <summary>
        /// Queries the async.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="queryType">Type of the query.</param>
        /// <param name="queryClassType">Type of the query class.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">name</exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<Response> QueryAsync(
            string name, QType queryType, QClassType queryClassType, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException("name");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            return await Resolver.QueryAsync(name, queryType, cancellationToken, queryClassType);
        }

        /// <summary>
        /// Queries the async.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="queryType">Type of the query.</param>
        /// <param name="queryClassType">Type of the query class.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<Response> QueryAsync(string name, QType queryType, QClassType queryClassType)
        {
            DnsPermission.Demand();

            return await QueryAsync(name, queryType, queryClassType, CancellationToken.None);
        }

        /// <summary>
        /// Queries the async.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="queryType">Type of the query.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<Response> QueryAsync(string name, QType queryType)
        {
            DnsPermission.Demand();

            return await QueryAsync(name, queryType, QClassType.IN, CancellationToken.None);
        }

        /// <summary>
        /// Queries the async.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="queryType">Type of the query.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">name is null.</exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<Response> QueryAsync(
            string name, QType queryType, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException("name");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            return await QueryAsync(name, queryType, QClassType.IN, cancellationToken);
        }

        /// <summary>
        /// Resolves the async.
        /// </summary>
        /// <param name="hostName">Name of the host.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">hostName</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">hostName;The length of parameter is greater than 126 characters.</exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<IPHostEntry> ResolveAsync(string hostName, CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(hostName))
            {
                throw new ArgumentNullException("hostName");
            }

            if (hostName.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostName", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            DnsPermission.Demand();

            return await Resolver.ResolveAsync(hostName, cancellationToken);
        }

        /// <summary>
        /// Resolves the async.
        /// </summary>
        /// <param name="hostName">Name of the host.</param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static async Task<IPHostEntry> ResolveAsync(string hostName)
        {
            DnsPermission.Demand();

            return await ResolveAsync(hostName, CancellationToken.None);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The on verbose event.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        private static void OnVerboseEvent(VerboseEventArgs e)
        {
            var handler = VerboseEvent;
            if (handler != null)
            {
                handler(null, e);
            }
        }

        #endregion
    }
}